Skip to content

Add navigation-aware undo/redo with onNavigate callback#21

Merged
TrystonPerry merged 3 commits into
masterfrom
claude/sweet-darwin-o23yhb
Jun 9, 2026
Merged

Add navigation-aware undo/redo with onNavigate callback#21
TrystonPerry merged 3 commits into
masterfrom
claude/sweet-darwin-o23yhb

Conversation

@TrystonPerry

Copy link
Copy Markdown
Collaborator

Summary

Adds support for navigation-aware undo/redo operations. When an undo action is tagged with a path, the store can invoke a navigation callback before reverting the change, returning users to the route where the change occurred.

Changes

  • Store API: Added setOnNavigate() method to FirestateStore to allow runtime updates to the navigation handler without recreating the store
  • Configuration: Added optional onNavigate callback to FirestateConfig and FirestateProviderProps
  • UndoManager integration: Passed onNavigate callback to createUndoManager so it can invoke navigation before undo/redo operations
  • Provider: Updated FirestateProvider to accept and manage the onNavigate callback, similar to existing onError pattern
  • Documentation: Added comprehensive README section explaining the feature with React Router example and usage patterns
  • Tests: Added 5 test cases covering:
    • Navigation on undo with path
    • Navigation on redo with path
    • No navigation when path is absent
    • Runtime handler replacement via setOnNavigate
    • Disabling navigation by setting handler to undefined

Implementation Details

The onNavigate callback is stored as a mutable reference in the store (like onError) to allow the provider to update it without recreating the store. This prevents subscription loss when consumers pass inline callbacks that change reference on every render. A stable wrapper delegates to this mutable reference, so the undo manager doesn't need recreation when the callback changes.

https://claude.ai/code/session_01DPzuA9BMs4erECovPMXbMW

claude added 3 commits June 9, 2026 21:03
The boolean flag was never read by any code — onNavigate is the
implemented API. Keeping it alongside onNavigate with a "supersedes"
comment implied live behavior that didn't exist.

https://claude.ai/code/session_01RhU9ifeoZ8Pb74HipfodF7
…shots

updateState and collection updateState used getMergedData() as the base
for constructing newLocalState. getMergedData() substitutes display-override
Timestamps at serverTimestamp() sentinel paths; a second update() would
clone those Timestamps into newLocalState, silently erasing the sentinel
from state.localState. Subsequent sync() would then ship a client Timestamp
to Firestore, re-introducing the C1 regression for any chained mutation.

The same problem affected setData and deleteDocument: their undo restore
snapshots called deepClone(getMergedData()), capturing the frozen Timestamp
instead of the sentinel; undo replay would call setDoc with a client
Timestamp rather than serverTimestamp.

Fix: use state.localState ?? state.syncState (raw state) as the mutation
base and undo snapshot source. getMergedData() is retained for display
(the null-check in updateState) and the undo diff comparisons now use
rawBase so sentinel-carrying diffs round-trip correctly through undo/redo.

Two regression tests added to firestate.integration.test.ts to pin both
paths.

https://claude.ai/code/session_01RhU9ifeoZ8Pb74HipfodF7
@TrystonPerry TrystonPerry merged commit 10a98d8 into master Jun 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants